package com.vision.sales.sv.serv;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import ar.com.fdvs.dj.domain.DJCalculation;

import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.db.mgr.DataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.util.ServletUtils;
import com.vision.core.sv.util.CollectionUtils;
import com.vision.sales.cm.data.Month;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.cm.db.data.Salesman;
import com.vision.sales.sv.db.mgr.SalesInvoiceManager;
import com.vision.sales.sv.db.mgr.SalesmanManager;
import com.vision.sales.sv.report.AbstractDataSource;
import com.vision.sales.sv.report.Report;
import com.vision.sales.sv.report.ReportWriter;
import com.vision.sales.sv.report.SalesmanSalesRow;

/**
 * Servlet for printing Salesman Sales report.
 * 
 * This servlet is accessible via the /print/salesman_sales path in the URL (see web.xml)
 * 
 * GET parameters:
 * <ul>
 * 	<li>title - the title that will appear in the report
 * 	<li>type - 'M' for monthly, 'A' annual. If parameter is unparseable, this defaults to 'A'.
 * 	<li>months - the months to include, separated by commas (ex: 1,2,3 for Jan to March). Applies when type is M.
 * 	<li>year - the year to include (ex: 2012). Applies when type is either M or A.
 * 	<li>companies - optional comma-separated list of company ids indicating which companies to include. If not specified, all companies will be included.</li>
 * 	<li>salesmans - optional comma-separated list of salesman ids indicating which salesmans to include. If not specified, all salesmans will be included.</li>
 * </ul>
 * 
 * Example:
 * <pre>/print/salesman_sales?title=Salesman%20Sales&type=A&year=2012&companies=1,2&salesmans=5,10,22&types=0,1,2</pre>
 * <pre>/print/salesman_sales?title=Salesman%20Sales&type=M&months=1,2,3&companies=1,2&salesmans=5,10,22&types=0,1,2</pre>
 * 
 * @author Mark
 */
@Service("print_salesman_sales")
public class PrintSalesmanSalesServlet extends HttpServlet {
	
	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(PrintSalesmanSalesServlet.class);
	
	@Autowired
	@Qualifier("salesmanManager")
	private DataManager<Salesman> salesmanManager;
	
	@Autowired
	@Qualifier("salesInvoiceManager")
	private DataManager<SalesInvoice> invoiceManager;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			ServletUtils.checkPermissions(SalesPermissions.ViewSalesmanSalesReport);
		} catch (RpcException e) {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not authorized to perform this operation.");
			return;
		} 
		
		// Parameters --
		log.debug("Parsing parameters from URL...");
		SalesmanSalesParams params = new SalesmanSalesParams(req);
		if (params.error != null) {
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, params.error);
			return;
		}
		
		try {
			// Build the Report
			log.debug("Building report...");
			Report report = new Report();
			report.setFilename((params.isAnnual() ? "Annual" : "Monthly") + " Salesman Sales Report " + params.year);
			report.setTitle(params.title + " " + params.year);
			report.setSize(params.size);
			report.setOrientation(params.orientation);

			// Columns and Grouping
			report.addStringColumn("salesman", "Salesman", 60, null);
			
			if (params.isAnnual()) {
				for (String m : Month.MONTHS) {
					report.addDoubleColumn(m, m, 30, DJCalculation.SUM);
				}
			} else {
				for (int m : params.months) {
					if (m > 0 && m < 13)
						report.addDoubleColumn(Month.MONTHS[m - 1], Month.MONTHS[m - 1], 30, DJCalculation.SUM);
				}
			}
			
			report.addDoubleColumn("total", "Total", 40, DJCalculation.SUM);
			
			// Data Source
			log.debug("Creating data source...");
			report.setDataSource(getDataSource(params));
			
			// Print and Write to Stream
			log.debug("Writing report to stream...");
			ReportWriter.write(report, resp);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The server was unable to process the request.");
		}
	}
	
	private JRDataSource getDataSource(SalesmanSalesParams params) {
		List<SalesInvoice> invoices = getInvoices(params);
		List<Salesman> salesmans = getSalesmen(params);
		
		Map<Long, SalesmanSalesRow> map = new HashMap<Long, SalesmanSalesRow>();
		for (Salesman salesman : salesmans) {
			map.put(salesman.getId(), new SalesmanSalesRow(salesman, new HashMap<String, Double>()));
		}
		
		for (SalesInvoice invoice : invoices) {
			SalesmanSalesRow row = map.get(invoice.getSalesman().getId());
			if (row == null) {
				log.warn("SalesmanSalesRow was not found.");
				map.put(invoice.getSalesman().getId(), new SalesmanSalesRow(invoice.getSalesman(), new HashMap<String, Double>()));
			}
			Date date = invoice.getDate();
			@SuppressWarnings("deprecation")
			String mo = Month.MONTHS[date.getMonth()];
			Double value = row.getSalesMap().get(mo);
			if (value == null)
				row.getSalesMap().put(mo, invoice.getGrossSales());
			else
				row.getSalesMap().put(mo, value + invoice.getGrossSales());
		}
		
		for (SalesmanSalesRow row : map.values())
			row.computeTotal();
				
		List<SalesmanSalesRow> list = new ArrayList<SalesmanSalesRow>(map.values());
		Collections.sort(list);
		
		JRDataSource ds = new AbstractDataSource<SalesmanSalesRow>(list) {
			@Override
			public Object getValue(SalesmanSalesRow object, String fieldName) {
				return object.get(fieldName);
			}
		};
		return ds;
	}
	
	@SuppressWarnings("unchecked")
	protected List<SalesInvoice> getInvoices(SalesmanSalesParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		invoiceManager = new SalesInvoiceManager();
		invoiceManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesInvoice.class);
		crit.eq("status", TransactionStatus.POSTED);
		
		crit.add(DateExtract.year("date", params.year));
		
		if (!params.isAnnual())
			crit.add(DateExtract.in("MONTH", "date", params.months));
		
		if (params.companyIds != null && !params.companyIds.isEmpty())
			crit.in("company.id", params.companyIds);
		
		if (params.salesmanIds != null && !params.salesmanIds.isEmpty())
			crit.in("salesman.id", params.salesmanIds);
		
		crit.asc("date");
		
		log.debug("Retrieving sales invoices...");
		List<SalesInvoice> invoices = invoiceManager.getTemplate().findByCriteria(crit.getDetached());
		return invoices;
	}
	
	protected List<Salesman> getSalesmen(SalesmanSalesParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		salesmanManager = new SalesmanManager();
		salesmanManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		log.debug("Retrieving salesmans...");
		List<Salesman> salesmans = null;
		if (params.salesmanIds == null || params.salesmanIds.isEmpty())
			salesmans = salesmanManager.getAll();
		else
			salesmans = salesmanManager.getByIds("id", params.salesmanIds);
		
		return salesmans;
	}
	
	// Inner classes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	static class SalesmanSalesParams extends PeriodSalesReportParams {
		
		List<Long> salesmanIds;
		
		public SalesmanSalesParams(HttpServletRequest req) {
			super(req);
			
			setDefaultTitle("Salesman Sales Report");
			
			String sSalesmanIds = req.getParameter("salesman");
			if (sSalesmanIds != null)
				salesmanIds = CollectionUtils.longsFromString(sSalesmanIds, ',');
		}
	}

}
